using System;
using System.Linq;
using System.Text.RegularExpressions;
using BrightSword.LightSaber;
using BrightSword.LightSaber.Module;

namespace Test.LightSaber.AppConsole
{
    internal class Program
    {
        private static void TestFloatingPointRegExp()
        {
            var rgTest = new [ ]
                         {
                             "0.0", "0", "100", "100.0", "3.1415", "+100.99", "-100.99", "-1.04E-03", "-1.04E+03", "-1.04E3", "1.04E3", "1E3",
                             "1.04E3.5", "-6.025E26", "+-1E3", "3.1.45", "E100", "1f", "w"
                         };

            foreach (var strTest in rgTest)
            {
                Console.WriteLine(
                    "{0} is{1}a floating point number",
                    strTest,
                    (Regex.IsMatch(strTest, Constants.C_REGEXP_FLOATINGPOINT, RegexOptions.IgnorePatternWhitespace)
                        ? " "
                        : " not "));
            }
        }

        private static void TestValidNameRegExp()
        {
            var rgTest = new [ ] { "a", "Aa", "aBcd", "_abcd", "ab1", "ab_1_2", "ab_1_2__", "__", "_0b", "a00", "hba1c", "0", "+ab", "1f", "00a" };

            foreach (var strTest in rgTest)
            {
                Console.WriteLine(
                    "{0} is{1}a valid name",
                    strTest,
                    (Regex.IsMatch(strTest, Constants.C_REGEXP_VALIDNAME, RegexOptions.IgnorePatternWhitespace)
                        ? " "
                        : " not "));
            }
        }

        private static void TestParser()
        {
            var rgValidExpressions = new [ ]
                                     {
                                         "( )", "(test a b)", "(test (f1 a b))", "(test (f1 a b) c)", "(test (f1 a b) c ( ))",
                                         "(+ (* 3.14159E2 -6.025E26) (/ 1.812E-27 -3.34E-35))", "(>= (f1 a b) /*(f2 c d)*/ 123.42E3)",
                                         "(test \"(f1 a b)\" (f2 c d))", "(test \'(f1 a b)\' (f2 c d))", "(a b (w ()) ())"
                                     };

            var rgInvalidExpressions = new [ ]
                                       {
                                           "((((< a", "(", "(\'this is supposed to be a single quoted string\")",
                                           "(\"this is supposed to be a double quoted string)", "(()", "((|||) b (&& a c) (! d)",
                                       };

            foreach (var str in rgValidExpressions)
            {
                Console.WriteLine("Must Succeed : ");
                str.Parse(ParseEventHandler, null);
            }
            foreach (var str in rgInvalidExpressions)
            {
                Console.WriteLine("Must Fail : ");
                str.Parse(ParseEventHandler, null);
            }
        }

        private static void ParseEventHandler(ParseEventArgs e)
        {
            Console.WriteLine("{0} : {1} - {2}, {3}", e.InputString, e.State, e.Token, e.TokenValue);
        }

        private static void TestEvaluation()
        {
            var callStack = new CallStack();
            callStack["height"] = 1.8;
            callStack["weight"] = 78;

            callStack["g"] = "female";
            callStack["hbp"] = 128;
            callStack["bt"] = 94;
            callStack["bmi"] = 24.1;

            var rgValidExpressions = new [ ]
                                     {
                                         "(defun square (x) (* x x))", "(defun double (x) (* 2 x))",
                                         @"((cond 
    ((== 1 2) 'square')
    (t 'double')) 5)", //                "(+ 1 1 1 1)",
//                "(+ (+ 1 2 3 4) 2 (* 3 4) 3)",
//                "(- (+ (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4)))",
//                "(* (- (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4)) -1 2)",
//                "(== 1 1)",
//                "(== (+ 1 1 1 1) (+ 3 1))",
//                "(== (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))",
//                "(== 'hello' 'world')",
//                "(== 'hello' 'Hello' 'HELLO')",
//                "(== (+ 1 0) 1)",
//                "(== (+ 1 0) (+ 1 1))",
//                "(== (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))",
//                "(unique 1 'false' true)",
//                "(unique (== 1 1) true 'false')",
//                "(unique (+ 1 0) 1)",
//                "(int? 12)",
//                "(number? 12)",
//                "(int? 3.14159265358979)",
//                "(number? 3.14159265358979)",
//                "(> 4 3 2 1)",
//                "(<= (+ 1 1 1 1) (+ 3 1.1) (* 2 2.1) (+ 2 3) (/ 16 3))",
//                "(descending 4 2 3 1)",
//                "(< 4 3 2 1)",
//                "(!= (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))",
//                "(> (+ 1 1 1 1) (+ 3 1.1) (/ 16 3))",
//                "(|| (== _unknown_symbol 'female') (&& (> hbp 120) (< bt 96) (>= bmi 24.0)))",
//                "(|| (== _unknown_symbol 'female') (&& (> hbp 120) (< _yet_another_unknown_symbol 96) (>= bmi 24.0)))",
//                "(unique 1 'test' 10 10)",
//                "(+ 32 ' Hello' \" World\")",
//                "(cat 'Hello World' ' ignore')",
//                "(concat 'nonsense' ' meep')",
//                "(&& (== g 'female') (&& (> hbp 120) (< bt 96) (>= bmi 24.0)))",
//                "(t)",                
//                "(or t true (true))",
//                "(or f false (false))",
//                "(or t nil)",
//                "(and t true (true))",
//                "(and f false (false))",
//                "(and t nil)",
//                "(|| _unknown_symbol true)",
//                "(&& _unknown_symbol true)",
//                "(today 'yyyy-MM-dd (dddd)')",
//                "(week_of_year (today))",
//                "(day_of_year (today))",
//                "(now)",
//                "(later_by (now) 5:30:22)",
//                "(earlier_by (now) 4:30:00)",
//                "(square 4)",
//                "(square (square 2))",
//                "(square (double 4))",
//                "(double (square 4))",
//                "(defun add_num (x y) (+ x y))",
//                "(add_num (square 20) (square 20))",
//                "((+ 's' 'qua' 're') 4)",
//                "(defun yesterday (f?) (date (- (today) 1) f))",
//                "(defun tomorrow (f?) (date (+ (today) 1) f))",
//                "(defun day_name (x) (date x 'dddd'))",
//                "(defun day_of_month (x) (date x 'd'))",
//                "(defun month (x) (date x 'MM'))",
//                "(defun short_month (x) (date x 'MMM'))",
//                "(defun long_month (x) (date x 'MMMM'))",
//                "(defun short_year (x) (date x 'yy'))",
//                "(defun long_year (x) (date x 'yyyy'))",
//                "(cond (t 'first') (f 'second'))",
//                "(cond (t 'first') (t 'second'))",
//                "(setq v (+ 2 3))",
//                "(setq w 100)",
//                "(setq z (+ v w))",
//                "(setq z (+ (+ 2 3) 25))",
//                "(atom v)",
//                "v",
//                @"((cond ((ascending 1 2) 'square') (t 'double')) v)",
//                @"(defun complex (x) (prog (setq w 23) ((if (> x 3) 'double' 'square') (sum w x))))", 
//                "(complex 2)",
//                "(complex 4)",
//                @"(defun fact(x) 
//    (cond 
//        ((> x 0) (* x (fact (- x 1))))
//        (t 1)))",
//                "(fact 3)",
//                "(fact 10)",
//                "(defun a (x) (cond ((== x 0) 0) (t (+ x (a (b x))))))",
//                "(defun b (x) (- x 1))",
//                "(a 10)",
//                "(cons 'a' 'b')",
//                "(cons 'a' nil)",
//                "(cons 'a' (list 1 2 3 4 5))",
//                "(setq w 25)",
//                "(atom w)",
//                "(cons (+ 1 2 3 4) 'w')",
//                "(cons (+ 1 2 3 4) w)",
//                "(atom? w)",
//                "(list? w)",
//                "(list? (cons (+ 1 2 3 4) w))",
//                "(int? w)",
//                "(cons 'maple' (list 'birch' 'oak' 'larch'))",
//                "(list 'maple' (list 'birch' 'oak' 'larch'))",
//                "(setq trees (list 'maple' 'birch' 'oak' 'larch'))",
//                "(car trees)",
//                "(cons (car trees) nil)",
//                "(cdr trees)",
//                "(cdr (car trees))",
//                "(car (cdr trees))",
//                "(atom? (car trees))",
//                "(if (atom? (car trees)) (list 'a list' 'result') 'nope')",
//                "(atom? (cdr trees))",
//                "(list? (car trees))",
//                "(list? (cdr trees))",
//                "(setq inner (cons (list 'inner child' 'inner sibling') 'outer sibling'))",
//                "(car inner)", "(list? (car inner))",
//                "(car (car inner))", "(atom? (car (car inner)))",
//                "(cdr (car inner))", "(list? (cdr (car inner)))",
//                "(car (cdr (car inner)))", "(atom? (car (cdr (car inner))))",
//                "(car (cdr inner))", "(atom? (car (cdr inner)))",
//                "(defun add (x y) (== x 'Hello World'))",
//                "(add 'Hello World' ' ignore')",
//                "(add 'nonsense' ' meep')",
//                "(nil? ())",
//                "(cons 'a' (list 'b' 'c' 'd' 'e' 'f'))",
//                "(cons (list 'a' 'b' 'c') (list 'd' 'e' 'f'))",
//                "(cons (list 'a' 'b' 'c') (list (list 'd' 'e' 'f')))",
//                "(cons (list 'b' 'c' 'd' 'e' 'f') 'a')",
//                "(cons 'f' nil)",
//                "(cons 'f' (list))",
//                "(nil? (list))",
//                "(nil? nil)",
//                "(list 'a' 'b' (list 'c' 'd') 'e' 'f')",
//                "(setq w 100)",
//                "w",
//                "(defun add_200 (x) (prog (setq w 200) (print w) (+ x w)))",
//                "w",
//                "(add_200 200)",
                                     };

            foreach (var str in rgValidExpressions)
            {
                Console.WriteLine("{0} evaluates to {1}", str, str.Parse().Eval(callStack).Value);
            }
        }

        private static void TestStackFrame()
        {
            var target = new CallStack();

            var frame = target.PushFrame();
            frame["bottom"] = "bottom";
            frame["hidden"] = "hidden below";

            frame = target.PushFrame();
            frame["top"] = "top";
            frame["hidden"] = "hidden top";

            Console.WriteLine(target["bottom"] ?? "null");
            Console.WriteLine(target["top"] ?? "null");
            Console.WriteLine(target["hidden"] ?? "null");

            target.PopFrame();
            Console.WriteLine(target["hidden"] ?? "null");

            target.PopFrame();
            Console.WriteLine(target["hidden"] ?? "null");

            target.PopFrame();
            Console.WriteLine(target["hidden"] ?? "null");

            target.PopFrame();
            Console.WriteLine(target["hidden"] ?? "null");
        }

        private static void TestLibrary()
        {
            Console.WriteLine(
                "{0} matches {1}",
                "sum",
                Library.GetRegisteredFunctionsByName("sum").Aggregate(
                    String.Empty,
                    (r, x) => r + (((r == String.Empty)
                        ? ":\n\t"
                        : "\n\t") + x.Method.DeclaringType.FullName + "." + x.Method.Name)));
            Console.WriteLine(
                "{0} matches {1}",
                "double",
                Library.GetRegisteredFunctionsByName("double").Aggregate(
                    String.Empty,
                    (r, x) => r + (((r == String.Empty)
                        ? ":\n\t"
                        : "\n\t") + x.Method.DeclaringType.FullName + "." + x.Method.Name)));
        }

        private static void TestReverse()
        {
            var callStack = new CallStack();

            Console.WriteLine(@"(defun make-list (x)
(cond
    ((list? x) x)
    (t (cons x nil))))".Parse().Eval(callStack).ToString());

            Console.WriteLine(
                @"(defun append (l x)
    (cond
        ((atom? l) (cons l (make-list x)))
        ((nil? l) (make-list x))
        (t (cons (car l) (append (cdr l) x)))
    ))"
                    .Parse().Eval(callStack).Value);

            Console.WriteLine(
                @"(defun reverse (l)
    (cond
        ((nil? l) l)
        ((atom? l) nil)
        ((list? (car l)) (append (reverse (cdr l)) (list (reverse (car l)))))
	    (t
            (append (reverse (cdr l)) (car l))
        )
    ))"
                    .Parse().Eval(callStack).Value);
            Console.WriteLine("(make-list (list 'a' 'b' 'c'))".Parse().Eval(callStack).ToString());
            Console.WriteLine("(make-list 'a')".Parse().Eval(callStack).ToString());
            Console.WriteLine("(append (list 'a' 'b' 'c') (list 'd' 'e' 'f') )".Parse().Eval(callStack).ToString());
            Console.WriteLine("(append (list 'a' 'b' (list 'c' 'd')) (list 'f' 'g'))".Parse().Eval(callStack).ToString());
            Console.WriteLine("(append (list 'h' 'i') (list 'f' 'g'))".Parse().Eval(callStack).ToString());
            Console.WriteLine("(append 'j' (list 'f' 'g' 'h'))".Parse().Eval(callStack).ToString());
            Console.WriteLine("(reverse (list 'a' 'b' 'c'))".Parse().Eval(callStack).ToString());
            Console.WriteLine("(reverse (list 'a' 'b' (list 'c' 'd') 'e' 'f'))".Parse().Eval(callStack).ToString());
        }

        private static void TestSqrt()
        {
            var lispProgram = new [ ]
                              {
                                  "(setq epsilon 1e-14)",
                                  "(defun sqrt-iter (guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x)))",
                                  "(defun improve (guess x) (average guess (/ x guess)))", "(defun average (x y) (/ (+ x y) 2))",
                                  "(defun abs (x) (if (< x 0) (- x) x))", "(defun square (x) (* x x))",
                                  "(defun good-enough? (guess x) (< (abs (- (square guess) x)) epsilon))", "(defun sqrt (x) (sqrt-iter 1.0 x))",
                              };

            var interpreter = new Interpreter().Initialize(lispProgram);

            for (var i = 0; i <= 25; i++)
            {
                var dtStart = DateTime.Now;
                var root = (double) interpreter.Execute("(sqrt {0})", i);
                var square = (double) interpreter.Execute("(square {0})", root);
                var ts = DateTime.Now.Subtract(dtStart);

                Console.WriteLine("sqrt({0}) = {1}, square(sqrt({0})) = {2}, {3} ms", i, root, square, ts.Milliseconds);
            }
        }

        private static void TestLambda()
        {
            var interpreter = new Interpreter();

            Console.WriteLine(interpreter.Execute("((lambda (x) (+ x 0)) {0})", "'1'"));
            Console.WriteLine(interpreter.Execute("((lambda (x) (+ x 3)) {0})", 1));
            Console.WriteLine(interpreter.Execute("((lambda (x) (+ x 3)) {0})", 3));
            Console.WriteLine(interpreter.Execute("((lambda (x) (+ x 3)) {0})", 15));
        }

        private static void TestMap()
        {
            var interpreter = new Interpreter();

            Console.WriteLine(interpreter.Execute("(map (lambda (x) (+ x 10)) 1 2 3 4 5)"));
        }

        private static void TestApply()
        {
            var interpreter = new Interpreter();

            Console.WriteLine(interpreter.Execute("(apply '+' 1 2 3 4 5 ' hello')"));
        }

        private static void TestDates()
        {
            var dateExpressions = new [ ]
                                  {
                                      "(defun yesterday (f?) (date (- (today) 1) f))", "(defun tomorrow (f?) (date (+ (today) 1) f))",
                                      "(defun day_name (x) (date x 'dddd'))", "(defun day_of_month (x) (date x 'd'))",
                                      "(defun month (x) (date x 'M'))", "(defun short_month (x) (date x 'MMM'))",
                                      "(defun long_month (x) (date x 'MMMM'))", "(defun short_year (x) (date x 'YY'))",
                                      "(defun long_year (x) (date x 'YYYY'))", "(defun days_to_sunday (d) (- 7 (day_of_week d)))", "(setq sunday 0)",
                                      "(setq monday 1)", "(setq tuesday 2)", "(setq wednesday 3)", "(setq thursday 4)", "(setq friday 5)",
                                      "(setq saturday 6)",
                                      @"(defun next_sunday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) sunday) 0 7) (- sunday (day_of_week d))))
    (date (+ d offset) f)))"
                                      , @"(defun last_sunday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (date (- (next_sunday d) 7) f)))"
                                      ,
                                      @"(defun next_monday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) monday) 0 7) (- monday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                      @"(defun next_tuesday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) tuesday) 0 7) (- tuesday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                      @"(defun next_wednesday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) wednesday) 0 7) (- wednesday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                      @"(defun next_thursday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) thursday) 0 7) (- thursday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                      @"(defun next_friday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) friday) 0 7) (- friday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                      @"(defun next_saturday (x? f?)
(prog
    (setq d (if (nil? x) today x))
    (setq offset (+ (if (< (day_of_week d) saturday) 0 7) (- saturday (day_of_week d))))
    (date (+ d offset) f)))"
                                      ,
                                  };

            var interpreter = new Interpreter().Initialize(dateExpressions);

            Console.WriteLine("Today is {0}", interpreter.Execute("(today 'u')"));
            Console.WriteLine("Last Sunday was {0}", interpreter.Execute("(last_sunday)"));
            Console.WriteLine("Next Sunday will be {0}", interpreter.Execute("(next_sunday)"));
            Console.WriteLine("Next Monday will be {0}", interpreter.Execute("(next_monday)"));
            Console.WriteLine("Next Tuesday will be {0}", interpreter.Execute("(next_tuesday)"));
            Console.WriteLine("Next Wednesday will be {0}", interpreter.Execute("(next_wednesday)"));
            Console.WriteLine("Next Thursday will be {0}", interpreter.Execute("(next_thursday)"));
            Console.WriteLine("Next Friday will be {0}", interpreter.Execute("(next_friday)"));
            Console.WriteLine("Next Saturday will be {0}", interpreter.Execute("(next_saturday)"));
        }

        private static void Main()
        {
            Library.Load(typeof (LispCommon));
            Library.Load(typeof (LispDate));
            Library.Load(typeof (LispNumber));
            Library.Load(typeof (LispBoolean));
            Library.Load(typeof (LispCompare));
            Library.Load(typeof (LispCore));

            TestFloatingPointRegExp();
            TestValidNameRegExp();
            TestParser();
            TestEvaluation();
            TestStackFrame();
            TestLibrary();
            TestReverse();
            TestSqrt();
            TestDates();
            TestLambda();
            TestMap();
            TestApply();
        }
    }
}